{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "99a80181",
   "metadata": {},
   "source": [
    "# Introduction to the Planner\n",
    "\n",
    "The Planner is one of the fundamental concepts of the Semantic Kernel.\n",
    "\n",
    "It makes use of the collection of native and semantic functions that have been registered to the kernel and using AI, will formulate a plan to execute the given ask.\n",
    "\n",
    "From our own testing, planner works best with more powerful models like `gpt4` but sometimes you might get working plans with cheaper models like `gpt-35-turbo`. We encourage you to implement your own versions of the planner and use different models that fit your user needs.\n",
    "\n",
    "Read more about planner [here](https://aka.ms/sk/concepts/planner)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "07eb35d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "!python -m pip install -U semantic-kernel==0.9.7b1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d548e40",
   "metadata": {},
   "outputs": [],
   "source": [
    "from services import Service\n",
    "\n",
    "# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
    "selectedService = Service.OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3852961c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.contents.chat_history import ChatHistory  # noqa: F401\n",
    "from semantic_kernel.functions.kernel_arguments import KernelArguments  # noqa: F401\n",
    "from semantic_kernel.prompt_template.input_variable import InputVariable  # noqa: F401"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "deff5675",
   "metadata": {},
   "source": [
    "Define your ASK. What do you want the Kernel to do?\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "925b4ae8",
   "metadata": {},
   "outputs": [],
   "source": [
    "ask = \"\"\"\n",
    "Tomorrow is Valentine's day. I need to come up with a few short poems.\n",
    "She likes Shakespeare so write using his style. She speaks French so write it in French.\n",
    "Convert the text to uppercase.\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b61bacf1",
   "metadata": {},
   "source": [
    "### Providing plugins to the planner\n",
    "\n",
    "The planner needs to know what plugins are available to it. Here we'll give it access to the `SummarizePlugin` and `WriterPlugin` we have defined on disk. This will include many semantic functions, of which the planner will intelligently choose a subset.\n",
    "\n",
    "You can also include native functions as well. Here we'll add the TextPlugin."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3161dcf",
   "metadata": {},
   "outputs": [],
   "source": [
    "import semantic_kernel as sk\n",
    "import semantic_kernel.connectors.ai.open_ai as sk_oai  # noqa: F401\n",
    "from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt\n",
    "from semantic_kernel.core_plugins.text_plugin import TextPlugin\n",
    "from semantic_kernel.utils.settings import openai_settings_from_dot_env, azure_openai_settings_from_dot_env\n",
    "\n",
    "kernel = sk.Kernel()\n",
    "service_id = \"default\"\n",
    "if selectedService == Service.OpenAI:\n",
    "    api_key, org_id = openai_settings_from_dot_env()\n",
    "    kernel.add_service(\n",
    "        sk_oai.OpenAIChatCompletion(\n",
    "            service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id\n",
    "        ),\n",
    "    )\n",
    "elif selectedService == Service.AzureOpenAI:\n",
    "    deployment, api_key, endpoint, api_version = azure_openai_settings_from_dot_env(include_api_version=True)\n",
    "    kernel.add_service(\n",
    "        sk_oai.AzureChatCompletion(\n",
    "            service_id=service_id,\n",
    "            deployment_name=deployment,\n",
    "            endpoint=endpoint,\n",
    "            api_key=api_key,\n",
    "            api_version=api_version,\n",
    "        ),\n",
    "    )\n",
    "\n",
    "plugins_directory = \"../../../prompt_template_samples/\"\n",
    "summarize_plugin = kernel.add_plugin(plugin_name=\"SummarizePlugin\", parent_directory=plugins_directory)\n",
    "writer_plugin = kernel.add_plugin(\n",
    "    plugin_name=\"WriterPlugin\",\n",
    "    parent_directory=plugins_directory,\n",
    ")\n",
    "text_plugin = kernel.add_plugin(plugin=TextPlugin(), plugin_name=\"TextPlugin\")\n",
    "\n",
    "shakespeare_func = KernelFunctionFromPrompt(\n",
    "    function_name=\"Shakespeare\",\n",
    "    plugin_name=\"WriterPlugin\",\n",
    "    prompt=\"\"\"\n",
    "{{$input}}\n",
    "\n",
    "Rewrite the above in the style of Shakespeare.\n",
    "\"\"\",\n",
    "    prompt_execution_settings=sk_oai.OpenAIChatPromptExecutionSettings(\n",
    "        service_id=service_id,\n",
    "        max_tokens=2000,\n",
    "        temperature=0.8,\n",
    "    ),\n",
    "    description=\"Rewrite the input in the style of Shakespeare.\",\n",
    ")\n",
    "kernel.add_function(plugin_name=\"WriterPlugin\", function=shakespeare_func)\n",
    "\n",
    "for plugin_name, plugin in kernel.plugins.items():\n",
    "    for function_name, function in plugin.functions.items():\n",
    "        print(f\"Plugin: {plugin_name}, Function: {function_name}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e8a9b6b7",
   "metadata": {},
   "source": [
    "# The Plan Object Model\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e50f8859",
   "metadata": {},
   "source": [
    "To build more advanced planners, we need to introduce a proper Plan object that can contain all the necessary state and information needed for high quality plans.\n",
    "\n",
    "To see what that object model is, look at (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/plan.py)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a0cb2a2",
   "metadata": {},
   "source": [
    "# Sequential Planner\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1c66d83",
   "metadata": {},
   "source": [
    "The sequential planner is an XML-based step-by-step planner. You can see the prompt used for it here (https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/sequential_planner/Plugins/SequentialPlanning/skprompt.txt)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e2e90624",
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.planners import SequentialPlanner\n",
    "\n",
    "planner = SequentialPlanner(kernel, service_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d537981",
   "metadata": {},
   "outputs": [],
   "source": [
    "sequential_plan = await planner.create_plan(goal=ask)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee2f462b",
   "metadata": {},
   "source": [
    "To see the steps that the Sequential Planner will take, we can iterate over them and print their descriptions\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e7007418",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"The plan's steps are:\")\n",
    "for step in sequential_plan._steps:\n",
    "    print(\n",
    "        f\"- {step.description.replace('.', '') if step.description else 'No description'} using {step.metadata.fully_qualified_name} with parameters: {step.parameters}\"\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4db5f844",
   "metadata": {},
   "source": [
    "Let's ask the sequential planner to execute the plan.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88411884",
   "metadata": {},
   "outputs": [],
   "source": [
    "result = await sequential_plan.invoke(kernel)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36d27aa0",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "789b651a",
   "metadata": {},
   "source": [
    "# Function Calling Stepwise Planner\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a4bbcc3",
   "metadata": {},
   "source": [
    "The Function Calling Stepwise Planner is based off the paper from MRKL (Modular Reasoning, Knowledge and Language) and is similar to other papers like ReACT (Reasoning and Acting in Language Models). At the core, the stepwise planner allows for the AI to form \"thoughts\" and \"observations\" and execute actions based off those to achieve a user's goal. This continues until all required functions are complete and a final output is generated.\n",
    "\n",
    "Please note that the Function Calling Stepwise Planner uses OpenAI function calling, and so it can only use either the AzureChatCompletion or the OpenAIChatCompletion service.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "771bafa2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import semantic_kernel as sk\n",
    "import semantic_kernel.connectors.ai.open_ai as sk_oai  # noqa: F401\n",
    "from semantic_kernel.utils.settings import openai_settings_from_dot_env, azure_openai_settings_from_dot_env\n",
    "\n",
    "kernel = sk.Kernel()\n",
    "service_id = \"default\"\n",
    "if selectedService == Service.OpenAI:\n",
    "    api_key, org_id = openai_settings_from_dot_env()\n",
    "    kernel.add_service(\n",
    "        sk_oai.OpenAIChatCompletion(\n",
    "            service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id\n",
    "        ),\n",
    "    )\n",
    "elif selectedService == Service.AzureOpenAI:\n",
    "    deployment, api_key, endpoint, api_version = azure_openai_settings_from_dot_env(include_api_version=True)\n",
    "    kernel.add_service(\n",
    "        sk_oai.AzureChatCompletion(\n",
    "            service_id=service_id,\n",
    "            deployment_name=deployment,\n",
    "            endpoint=endpoint,\n",
    "            api_key=api_key,\n",
    "            api_version=api_version,\n",
    "        ),\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0a00bde",
   "metadata": {},
   "source": [
    "Let's create a sample `EmailPlugin` that simulates handling a request to `get_email_address()` and `send_email()`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cb43d0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated\n",
    "from semantic_kernel.functions.kernel_function_decorator import kernel_function\n",
    "\n",
    "\n",
    "class EmailPlugin:\n",
    "    \"\"\"\n",
    "    Description: EmailPlugin provides a set of functions to send emails.\n",
    "\n",
    "    Usage:\n",
    "        kernel.import_plugin_from_object(EmailPlugin(), plugin_name=\"email\")\n",
    "\n",
    "    Examples:\n",
    "        {{email.SendEmail}} => Sends an email with the provided subject and body.\n",
    "    \"\"\"\n",
    "\n",
    "    @kernel_function(name=\"SendEmail\", description=\"Given an e-mail and message body, send an e-email\")\n",
    "    def send_email(\n",
    "        self,\n",
    "        subject: Annotated[str, \"the subject of the email\"],\n",
    "        body: Annotated[str, \"the body of the email\"],\n",
    "    ) -> Annotated[str, \"the output is a string\"]:\n",
    "        \"\"\"Sends an email with the provided subject and body.\"\"\"\n",
    "        return f\"Email sent with subject: {subject} and body: {body}\"\n",
    "\n",
    "    @kernel_function(name=\"GetEmailAddress\", description=\"Given a name, find the email address\")\n",
    "    def get_email_address(\n",
    "        self,\n",
    "        input: Annotated[str, \"the name of the person\"],\n",
    "    ):\n",
    "        email = \"\"\n",
    "        if input == \"Jane\":\n",
    "            email = \"janedoe4321@example.com\"\n",
    "        elif input == \"Paul\":\n",
    "            email = \"paulsmith5678@example.com\"\n",
    "        elif input == \"Mary\":\n",
    "            email = \"maryjones8765@example.com\"\n",
    "        else:\n",
    "            input = \"johndoe1234@example.com\"\n",
    "        return email"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9feef46b",
   "metadata": {},
   "source": [
    "We'll add this new plugin to the kernel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "032d5981",
   "metadata": {},
   "outputs": [],
   "source": [
    "kernel.add_plugin(plugin_name=\"EmailPlugin\", plugin=EmailPlugin())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "effdf3ab",
   "metadata": {},
   "source": [
    "Let's also add a couple more plugins."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "abe150e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.core_plugins.math_plugin import MathPlugin\n",
    "from semantic_kernel.core_plugins.time_plugin import TimePlugin\n",
    "\n",
    "kernel.add_plugin(plugin_name=\"MathPlugin\", plugin=MathPlugin())\n",
    "kernel.add_plugin(plugin_name=\"TimePlugin\", plugin=TimePlugin())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06796ade",
   "metadata": {},
   "source": [
    "We will define our FunctionCallingStepPlanner and the questions we want to ask."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "06d08549",
   "metadata": {},
   "outputs": [],
   "source": [
    "from semantic_kernel.planners.function_calling_stepwise_planner import (\n",
    "    FunctionCallingStepwisePlanner,\n",
    "    FunctionCallingStepwisePlannerOptions,\n",
    ")\n",
    "\n",
    "questions = [\n",
    "    \"What is the current hour number, plus 5?\",\n",
    "    \"What is 387 minus 22? Email the solution to John and Mary.\",\n",
    "    \"Write a limerick, translate it to Spanish, and send it to Jane\",\n",
    "]\n",
    "\n",
    "options = FunctionCallingStepwisePlannerOptions(\n",
    "    max_iterations=10,\n",
    "    max_tokens=4000,\n",
    ")\n",
    "\n",
    "planner = FunctionCallingStepwisePlanner(service_id=service_id, options=options)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27ed7874",
   "metadata": {},
   "source": [
    "Let's loop through the questions and invoke the planner."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d00c6f71",
   "metadata": {},
   "outputs": [],
   "source": [
    "for question in questions:\n",
    "    result = await planner.invoke(kernel, question)\n",
    "    print(f\"Q: {question}\\nA: {result.final_answer}\\n\")\n",
    "\n",
    "    # Uncomment the following line to view the planner's process for completing the request\n",
    "    # print(f\"Chat history: {result.chat_history}\\n\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
